// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include "tests.h"

#include <err.h>
#include <inttypes.h>
#include <platform.h>

#include <kernel/mp.h>
#include <kernel/thread.h>
#include <lib/unittest/unittest.h>

#include <zircon/types.h>

static int resume_cpu_test_thread(void* arg) {
    *reinterpret_cast<uint*>(arg) = arch_curr_cpu_num();
    return 0;
}

// "Unplug" online secondary (non-BOOT) cores
static zx_status_t unplug_all_cores() {
    cpu_mask_t cpumask = mp_get_online_mask() & ~cpu_num_to_mask(BOOT_CPU_ID);
    return mp_unplug_cpu_mask(cpumask);
}

static zx_status_t hotplug_core(uint i) {
    cpu_mask_t cpumask = cpu_num_to_mask(i);
    return mp_hotplug_cpu_mask(cpumask);
}

static unsigned get_num_cpus_online() {
    unsigned count = 0;
    cpu_mask_t online = mp_get_online_mask();
    while (online) {
        online >>= 1;
        ++count;
    }
    return count;
}

// Unplug all cores (except for Boot core), then hotplug
// the cores one by one and make sure that we can schedule
// tasks on that core.
static bool mp_hotplug_test() {
    BEGIN_TEST;

// Hotplug is only implemented for x64.
#if !defined(__x86_64__)
    printf("skipping test mp_hotplug, hotplug only suported on x64\n");
    END_TEST;
#endif
    uint num_cores = get_num_cpus_online();
    if (num_cores < 2) {
        printf("skipping test mp_hotplug, not enough online cpus\n");
        END_TEST;
    }
    thread_migrate_to_cpu(BOOT_CPU_ID);
    // "Unplug" online secondary (non-BOOT) cores
    ASSERT_EQ(unplug_all_cores(), ZX_OK, "unplugging all cores failed");
    for (uint i = 0; i < num_cores; i++) {
        if (i == BOOT_CPU_ID) {
            continue;
        }
        // hotplug this core.
        ASSERT_EQ(hotplug_core(i), ZX_OK, "hotplugging core failed");
        // Create a thread, affine it to the core just hotplugged
        // and make sure the thread does get scheduled there.
        uint running_core;
        thread_t* nt = thread_create("resume-test-thread",
                                     resume_cpu_test_thread, &running_core,
                                     DEFAULT_PRIORITY);
        ASSERT_NE(nullptr, nt, "Thread create failed");
        thread_set_cpu_affinity(nt, cpu_num_to_mask(i));
        thread_resume(nt);
        ASSERT_EQ(thread_join(nt, nullptr, ZX_TIME_INFINITE), ZX_OK,
                  "thread join failed");
        ASSERT_EQ(i, running_core, "Thread not running on hotplugged core");
    }
    END_TEST;
}

UNITTEST_START_TESTCASE(mp_hotplug_tests)
UNITTEST("test unplug and hotplug cores one by one", mp_hotplug_test)
UNITTEST_END_TESTCASE(mp_hotplug_tests, "hotplug",
                      "Tests for unplugging and hotplugging cores");
